/* File: MoveStrategy.java
 * Purpose: Defines an organism's algorithm for movement
 * Author: Team Karp
 * Date: 12/13/07
 * Revisions: 12/13/07, Adam Patterson - Initial Version
 *           [2008-01-22, Adam Patterson - Implemented scripting function]
 *           [2008-02-15, Ryan J. Bovorasmy - Rewrote scripting and implemented simple move algorithm in javascript]
 * Additional Requirements: 
 */
package protovitae.data;

import java.util.ArrayList;
import javax.script.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * Defines an organism's algorithm for movement.
 * @version 1.0
 * @created 06-Dec-2007 16:19:10
 */
public class MoveStrategy extends GeneActionStrategy {

    /**
     * Creates a default algorithm.
     */
    public MoveStrategy() {
        // Right now, the default move algorithm is:
        
        StringBuilder builder = new StringBuilder();
        builder.append("var groupProb = 0.0;");
        builder.append("var randomProb = 8.0 / (Math.random() * (parent.getColor().getRGB() % 5));");
        builder.append("var normalProb = 1.0 - randomProb;");
        // Keep moving diagonally. 
        builder.append("var r = Math.random()*2;");
        builder.append("if (r < 1) {");
            builder.append("var s = Math.random()*2;");
            builder.append("x = (s < 1)? -x: x;");
            builder.append("y = (s > 1)? -y: y;");
        builder.append("} else { ");            
            builder.append("var xt = parent.getDirection().getX() * parent.getAttribute(parent.getAttributeOrdinalFromString(\"FORTITUDE\")).getCurrent() + xPos;\n");
            builder.append("var yt = parent.getDirection().getY() * parent.getAttribute(parent.getAttributeOrdinalFromString(\"FORTITUDE\")).getCurrent() + yPos;\n");
            builder.append("var zt = parent.getDirection().getZ() * parent.getAttribute(parent.getAttributeOrdinalFromString(\"FORTITUDE\")).getCurrent() + zPos;\n");
            builder.append("if(!environment.locationAvailable(xt,yt,zt,parent)){\n");
                builder.append("var r = Math.random()*2;");
                builder.append("if(r<1){x=-x;}\n");
                builder.append("if(r>1){y=-y;}\n");
            builder.append("}\n");
        builder.append("}\n");
        

        //tend to the SE
//        builder.append("if(xPos>3 && yPos>3){\n");
//        builder.append("var r = Math.random()*3.0; \n");
//        builder.append("if(r<1){x = -.25; \n");
//        builder.append("}else if(r>1 && r<2){x = 0; \n");
//        builder.append("}else{x = .25;} \n");
//        builder.append("r = Math.random()*3; \n");
//        builder.append("if(r<1){ y = -.25; \n");
//        builder.append("}else if(r>1 && r<2){y = 0; \n");
//        builder.append("}else{y = .25;} \n");
//        builder.append("}else{\n");
//        builder.append("var r = Math.random()*3.0;\n");
//        builder.append("if(r<1){\n");
//        builder.append("x = 2;\n");
//        builder.append("}else if(r>1 && r<2){\n");
//        builder.append("y = 2;\n");
//        builder.append("}else{\n");
//        builder.append("x = 2;y = 2;\n");
//        builder.append("}}\n");

        try {
            parse(builder.toString()); //Set code
        } catch (Exception e) {
        }
    }

    /**
     * This method is called to execute the GeneActionStrategy.
     * @param information - the environment the organism is in
     * @param parent - the owning ConcreteOrganism
     * @return vector in which to move (Position object)
     */
    @Override
    public Object execute(Object information, ConcreteOrganism parent) {
        Position vectorToMove;
        
        if (actionScript != null) {
            try {
                ArrayList<Double> pos = (ArrayList<Double>) actionScript.invokeFunction("move", (Simulation) information, parent,this);
                vectorToMove = new Position(pos.get(0), pos.get(1), pos.get(2));
            } catch (NoSuchMethodException nsme) {
                nsme.printStackTrace();
                vectorToMove = new Position(0, 0, 0);
            } catch (ScriptException se) {
                se.printStackTrace();
                vectorToMove = new Position(0, 0, 0);
            } catch (Exception e) {
                e.printStackTrace();
                vectorToMove = new Position(0, 0, 0);
            }
        } else {
            vectorToMove = new Position(0, 0, 0);
        }

        //return modified food source
        return vectorToMove;
    }

    /**
     * Saves the script.
     * @param parent
     */
    public void save(Element parentElm, Document doc) {
        Element move = doc.createElement("move");
        
        Element xmlcode = doc.createElement("code");
        xmlcode.appendChild(doc.createTextNode(code));
        move.appendChild(xmlcode);
        
        //Write genetic byte array
        Element gene = doc.createElement("genedata");
        for(Byte thebyte : geneticInformation){
            Element belm = doc.createElement("b");
            gene.appendChild(doc.createTextNode(thebyte.toString()));
            gene.appendChild(belm);
        }
        move.appendChild(gene);
        
        parentElm.appendChild(move);
    }

    public void load(Element input) {
        try{
            NodeList myroot = input.getElementsByTagName("move");
            NodeList mycode = ((Element)myroot.item(0)).getElementsByTagName("code");
            NodeList mydata = ((Element)myroot.item(0)).getElementsByTagName("genedata");
            
            parse(mycode.item(0).getTextContent()); //Calling parse will throw an exception for us if bad code. This is good.
            for(int i=0; i<mydata.getLength(); i++){
                if(mydata.item(i).getNodeName().equals("b"))
                   geneticInformation.add(new Byte(Byte.parseByte(mydata.item(i).getTextContent())));
            }
        }
        catch(Exception e){
            e.printStackTrace(); //A bug occured
        }
    }
    
    

    /**
     * This parses the code string and stores it in param code.
     * @param code The javascript code. 
     * @throws javax.script.ScriptException
     */
    public void parse(String code) throws ScriptException {
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");

        //load the code into a function with parameters to use

        String scriptedFunctionality = "importPackage(java.util);\n";
        scriptedFunctionality += "function move(environment,parent,gene) {\n";
        scriptedFunctionality += "var x = parent.getDirection().getX();\n";//simplify the user's end of the movement to x, y and z vector components
        scriptedFunctionality += "var y = parent.getDirection().getY();\n";
        scriptedFunctionality += "var z = parent.getDirection().getZ();\n";
        scriptedFunctionality += "var xPos = parent.getPosition().getX();\n";//simplify the user's end of the movement to x, y and z vector components
        scriptedFunctionality += "var yPos = parent.getPosition().getY();\n";
        scriptedFunctionality += "var zPos = parent.getPosition().getZ();\n";
        scriptedFunctionality += "var radius = environment.getRadiusOfMovement();\n";
        scriptedFunctionality += code;
        scriptedFunctionality += "\nvar pos = new ArrayList();\n"; //Added \n here, because you can't rely on the user to end their code with one!
        scriptedFunctionality += "pos.add(x);\n";
        scriptedFunctionality += "pos.add(y);\n";
        scriptedFunctionality += "pos.add(z);\n";
        scriptedFunctionality += "return pos;\n";
        scriptedFunctionality += "}";

        jsEngine.eval(scriptedFunctionality);
        actionScript = (Invocable) jsEngine;
        this.code = code; //Set the code last. If Eval fails, function will bail early.
    }
}
